//  Abstract:
//    See opf.h
//
//  Reference:
//    See opf.h
//
//  Design Notes:
//    See opf.h
//
//  Disclaimer:
//    Copyright (C) 2008, Janice Law <janicel700@gmail.com>.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//  Change Log:
//
#include <libxml++/libxml++.h>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include "opf.h"
#include "wrapper-xslt.h"

using namespace std;
using Glib::ustring;

namespace Epub
{
Opf::Opf()
{
    nsMap.insert(pair<ustring, ustring>("dc",
            "http://purl.org/dc/elements/1.1/"));
    nsMap.insert(pair<ustring, ustring>("opf", "http://www.idpf.org/2007/opf"));
    opf_xml = shared_ptr<xmlpp::Document>(new xmlpp::Document);
    xmlpp::Element* current_node;
    opf_xml->create_root_node("package", "http://www.idpf.org/2007/opf");
    opf_xml->get_root_node()->set_namespace_declaration("http://www.idpf.org/2007/opf", "opf");
    opf_xml->get_root_node()->set_namespace_declaration("http://purl.org/dc/elements/1.1/",
            "dc");
    opf_xml->get_root_node()->set_attribute("version", "2.0");
    opf_xml->get_root_node()->set_attribute("unique-identifier", "BookId");
    current_node = opf_xml->get_root_node()->add_child("metadata");
    current_node = current_node->add_child("identifier", "dc");
    current_node->set_attribute("id", "BookId");
    opf_xml->get_root_node()->add_child("manifest");
    current_node = opf_xml->get_root_node()->add_child("spine");
    current_node->set_attribute("toc", "ncx");
    opf_xml->get_root_node()->add_child("guide");
    Opf::Refresh();
}

Opf::Opf(shared_ptr<xmlpp::Document> opf_document)
{
    nsMap.insert(pair<ustring, ustring>("dc",
            "http://purl.org/dc/elements/1.1/"));
    nsMap.insert(pair<ustring, ustring>("opf", "http://www.idpf.org/2007/opf"));
    opf_document->get_root_node()->set_namespace_declaration("http://www.idpf.org/2007/opf");
    opf_document->get_root_node()->set_namespace_declaration("http://www.idpf.org/2007/opf", "opf");
    opf_document->get_root_node()->set_namespace_declaration("http://purl.org/dc/elements/1.1/",
            "dc");
    xmlpp::Element* current_node;
    current_node = (xmlpp::Element*)opf_document->get_root_node()->get_children("metadata").front();
    opf_xml = shared_ptr<xmlpp::Document>(new xmlpp::Document);
    scoped_ptr<Xslt::Xslt> fixup_xslt(new Xslt::Xslt);
    opf_xml->create_root_node_by_import(fixup_xslt->Transform(opf_document)->get_root_node());
}

Opf::~Opf()
{
}

//  TODO:
//      Fix this horrible hack to get libxml2 to recoginzing changes in namespace
//      Currently force a reparse on all structure changes
void Opf::Refresh()
{
    scoped_ptr<xmlpp::DomParser> parser(new xmlpp::DomParser);
    parser->set_substitute_entities();
    parser->parse_memory(opf_xml->write_to_string());
    opf_xml = shared_ptr<xmlpp::Document>(new xmlpp::Document);
    opf_xml->create_root_node_by_import(parser->get_document()->get_root_node());
}

void Opf::Replace(ustring xpath, ustring node, ustring_1_list param)
{
    xmlpp::NodeSet parent = opf_xml->get_root_node()->find(xpath, nsMap);
    xmlpp::NodeSet oldData = parent[0]->find(node, nsMap);
    vector<xmlpp::Node*>::iterator it;
    for (it = oldData.begin(); it != oldData.end(); it++)
    {
        parent[0]->remove_child(*it);
    }
    list<ustring_1_entry>::const_iterator param_entry;
    xmlpp::Element* current_entry;
    for (param_entry=param.begin(); param_entry!=param.end(); param_entry++)
    {
        current_entry = parent[0]->add_child(node);
        current_entry->set_child_text(param_entry->get<0>());
    }
    Opf::Refresh();
}

void Opf::Replace(ustring xpath, ustring node, ustring attrName1,
        ustring_2_list param)
{
    xmlpp::NodeSet parent = opf_xml->get_root_node()->find(xpath, nsMap);
    xmlpp::NodeSet oldData = parent[0]->find(node, nsMap);
    vector<xmlpp::Node*>::iterator it;
    for (it = oldData.begin(); it != oldData.end(); it++)
    {
        parent[0]->remove_child(*it);
    }
    list<ustring_2_entry>::const_iterator param_entry;
    xmlpp::Element* current_entry;
    for (param_entry=param.begin(); param_entry!=param.end(); param_entry++)
    {
        current_entry = parent[0]->add_child(node);
        current_entry->set_child_text(param_entry->get<0>());
        current_entry->set_attribute(attrName1, param_entry->get<1>());
    }
    Opf::Refresh();
}

void Opf::Replace(ustring xpath, ustring node, ustring attrName1,
        ustring attrName2, ustring_3_list param)
{
    xmlpp::NodeSet parent = opf_xml->get_root_node()->find(xpath, nsMap);
    xmlpp::NodeSet oldData = parent[0]->find(node, nsMap);
    vector<xmlpp::Node*>::iterator it;
    for (it = oldData.begin(); it != oldData.end(); it++)
    {
        parent[0]->remove_child(*it);
    }
    list<ustring_3_entry>::const_iterator param_entry;
    xmlpp::Element* current_entry;
    for (param_entry=param.begin(); param_entry!=param.end(); param_entry++)
    {
        current_entry = parent[0]->add_child(node);
        current_entry->set_child_text(param_entry->get<0>());
        current_entry->set_attribute(attrName1, param_entry->get<1>());
        current_entry->set_attribute(attrName2, param_entry->get<2>());
    }
    Opf::Refresh();
}

Opf::ustring_1_list Opf::Query(ustring queryString)
{
    Opf::ustring_1_list return_val;
    xmlpp::NodeSet result = opf_xml->get_root_node()->find(queryString, nsMap);
    vector<xmlpp::Node*>::iterator it;
    xmlpp::Element* current_element;
    for (it = result.begin(); it != result.end(); it++)
    {
        current_element = dynamic_cast<xmlpp::Element*>(*it);
        return_val.push_back(Opf::ustring_1_entry(current_element->get_child_text()
                == NULL ? "" : current_element->get_child_text()->get_content() ));
    }
    return return_val;
}

Opf::ustring_2_list Opf::Query(ustring queryString, ustring attrName1)
{
    Opf::ustring_2_list return_val;
    xmlpp::NodeSet result = opf_xml->get_root_node()->find(queryString, nsMap);
    vector<xmlpp::Node*>::iterator it;
    xmlpp::Element* current_element;
    for (it = result.begin(); it != result.end(); it++)
    {
        current_element = dynamic_cast<xmlpp::Element*>(*it);
        xmlpp::NodeSet attr1 = current_element->find("./@"+attrName1, nsMap);
        return_val.push_back(Opf::ustring_2_entry(
                current_element->get_child_text() == NULL ? ""
                        : current_element->get_child_text()->get_content(), attr1.empty() ? "" : ((xmlpp::Attribute*)attr1[0])->get_value() ));
    }
    return return_val;
}

Opf::ustring_3_list Opf::Query(ustring queryString, ustring attrName1,
        ustring attrName2)
{
    Opf::ustring_3_list return_val;
    xmlpp::NodeSet result = opf_xml->get_root_node()->find(queryString, nsMap);
    vector<xmlpp::Node*>::iterator it;
    xmlpp::Element* current_element;
    for (it = result.begin(); it != result.end(); it++)
    {
        current_element = dynamic_cast<xmlpp::Element*>(*it);
        xmlpp::NodeSet attr1 = current_element->find("./@"+attrName1, nsMap);
        xmlpp::NodeSet attr2 = current_element->find("./@"+attrName2, nsMap);
        return_val.push_back(Opf::ustring_3_entry(
                current_element->get_child_text() == NULL ? ""
                        : current_element->get_child_text()->get_content(), attr1.empty() ? "" : ((xmlpp::Attribute*)attr1[0])->get_value(),
                attr2.empty() ? "" : ((xmlpp::Attribute*)attr2[0])->get_value() ));
    }
    return return_val;
}

Opf::ustring_1_list Opf::get_title()
{
    return Query("/opf:package/opf:metadata/dc:title");
}

void Opf::set_title(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_1_list Opf::get_language()
{
    return Query("/opf:package/opf:metadata/dc:language");
}

void Opf::set_language(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_3_list Opf::get_identifier()
{
    return Query("/opf:package/opf:metadata/dc:identifier", "id", "scheme");
}

void Opf::set_identifier(Opf::ustring_3_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", "id", "scheme", param);
}

Opf::ustring_3_list Opf::get_creator()
{
    return Query("/opf:package/opf:metadata/dc:creator", "file-as", "role");
}

void Opf::set_creator(Opf::ustring_3_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", "file-as", "role", param);
}

Opf::ustring_1_list Opf::get_subject()
{
    return Query("/opf:package/opf:metadata/dc:subject");
}

void Opf::set_subject(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_1_list Opf::get_description()
{
    return Query("/opf:package/opf:metadata/dc:description");
}

void Opf::set_description(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_1_list Opf::get_publisher()
{
    return Query("/opf:package/opf:metadata/dc:publisher");
}

void Opf::set_publisher(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_3_list Opf::get_contributor()
{
    return Query("/opf:package/opf:metadata/dc:contributor", "file-as", "role");
}

void Opf::set_contributor(Opf::ustring_3_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", "file-as", "role", param);
}

Opf::ustring_2_list Opf::get_date()
{
    return Query("/opf:package/opf:metadata/dc:date", "event");
}

void Opf::set_date(Opf::ustring_2_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", "event", param);
}

Opf::ustring_1_list Opf::get_type()
{
    return Query("/opf:package/opf:metadata/dc:type");
}

void Opf::set_type(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_1_list Opf::get_format()
{
    return Query("/opf:package/opf:metadata/dc:format");
}

void Opf::set_format(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_1_list Opf::get_source()
{
    return Query("/opf:package/opf:metadata/dc:source");
}

void Opf::set_source(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_1_list Opf::get_relation()
{
    return Query("/opf:package/opf:metadata/dc:relation");
}

void Opf::set_relation(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_1_list Opf::get_coverage()
{
    return Query("/opf:package/opf:metadata/dc:coverage");
}

void Opf::set_coverage(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}

Opf::ustring_1_list Opf::get_rights()
{
    return Query("/opf:package/opf:metadata/dc:rights");
}

void Opf::set_rights(Opf::ustring_1_list param)
{
    Replace("/opf:package/opf:metadata", "dc:title", param);
}
} // Epub
